#include <gtest/gtest.h>

#include <optional>

namespace test {

template<typename T>
class Iter {
public:
  virtual std::optional<T> next()  = 0;

  // ****************** function as args *********
  template<typename F = std::function<void(const T &)> >
  void foreach(const F & f){ 
    for(auto && o = next(); o ; o = next())
      f(o.value());
  }

  virtual void foreachExt(const std::function<void(const T&)> & f){ 
    for(auto && o = next(); o ; o = next())
      f(o.value());
  }
  // ****************** function as args End *********
  
  
  template<typename B>
  std::optional<B> findMap(const std::function<std::optional<B>(const T&)> & f) {
    for(auto && o = next(); o; o = next()){
      auto && ret = f(o.value());
      if(ret.has_value()){
        return ret;
      }
    }
    return std::nullopt;
  }


};

class TableIter : public Iter<int>{
public:
  std::optional<int> next() override {
    if(_number < 3){
      return std::make_optional<int>(_number++);
    }
    return std::nullopt;
  }

  void foreachExt(const std::function<void(const int&)> & f) override{ 
    std::cout << "this is override virtual function from TableIter" << std::endl;
    Iter<int>::foreachExt(f);
  }

  //override template member function from Subclass 
  template<typename B>
  std::optional<B> findMap(const std::function<std::optional<B>(const int &)> & f) {
    std::cout << "template function override from TableIter" << std::endl;
    return Iter<int>::findMap<B>(f);
  }
private:
  int _number = 0;
};



}//namespace test


using namespace test;

TEST(IterTest, test){
  ASSERT_EQ(0, TableIter().next());

  TableIter().foreach([](const int & i){
    std::cout << i << std::endl;
  });

  TableIter().foreachExt([](const int & i){
    std::cout << i << std::endl;
  });


	ASSERT_EQ(std::make_optional<std::string>("1"),
	  TableIter()
    .findMap<std::string>([](const int & v) -> std::optional<std::string>{ 
        if(v == 1){
          return std::make_optional<std::string>(std::to_string(v));
        }
        return std::nullopt;
    })
  );

}
